package com.learn.security.oauth2.auth.server.config;

import com.learn.security.oauth2.auth.server.config.oauth2.OAuth2PasswordAuthenticationConverter;
import com.learn.security.oauth2.auth.server.config.oauth2.OAuth2PasswordAuthenticationProvider;
import com.learn.security.oauth2.auth.server.utils.Jwks;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.util.Arrays;

/**
 * 授权服务配置
 *
 * @author knight
 */
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
@AllArgsConstructor
public class AuthorizationServerConfig {

	private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";

	/** 用户详细信息服务 */
	private final UserDetailsService userDetailsService;

	/**
	 * 授权服务器安全过滤器链
	 * @param http http
	 * @return {@link SecurityFilterChain}
	 * @throws Exception 异常
	 */
	// @formatter:off
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer<>();
        authorizationServerConfigurer
                .authorizationEndpoint(authorizationEndpoint ->
                        // 自定义consent页面
                        authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI))
				.tokenEndpoint(tokenEndpoint -> {
					tokenEndpoint
							.accessTokenRequestConverter(new DelegatingAuthenticationConverter(Arrays.asList(
									new OAuth2AuthorizationCodeAuthenticationConverter(),
									new OAuth2RefreshTokenAuthenticationConverter(),
									new OAuth2ClientCredentialsAuthenticationConverter(),
									new OAuth2PasswordAuthenticationConverter()
							)))
					;

				});

        RequestMatcher endpointsMatcher = authorizationServerConfigurer
                .getEndpointsMatcher();

        http
                .requestMatcher(endpointsMatcher)
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests.anyRequest().authenticated()
                )
                .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
                .apply(authorizationServerConfigurer);
		SecurityFilterChain securityFilterChain = http.formLogin(Customizer.withDefaults()).build();
		//
		addCustomOAuth2PasswordAuthenticationProvider(http);
		return securityFilterChain;
	}
	// @formatter:on

	/**
	 * 添加自定义oauth2密码身份验证提供者
	 * @param http http
	 */
	private void addCustomOAuth2PasswordAuthenticationProvider(HttpSecurity http) {
		ProviderSettings providerSettings = http.getSharedObject(ProviderSettings.class);
		OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
		JwtEncoder jwtEncoder = http.getSharedObject(JwtEncoder.class);
		// 构建OAuth2PasswordAuthenticationProvider
		OAuth2PasswordAuthenticationProvider passwordAuthenticationProvider = new OAuth2PasswordAuthenticationProvider(
				userDetailsService, providerSettings, jwtEncoder, authorizationService);
		http.authenticationProvider(passwordAuthenticationProvider);
	}

	/**
	 * jwk源
	 * @return {@link JWKSource<SecurityContext>}
	 */
	@Bean
	public JWKSource<SecurityContext> jwkSource() {
		RSAKey rsaKey = Jwks.generateRsa();
		JWKSet jwkSet = new JWKSet(rsaKey);
		return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
	}

	/**
	 * 可以配置oauth端点
	 * @return {@link ProviderSettings}
	 */
	@Bean
	public ProviderSettings providerSettings() {
		return ProviderSettings.builder().issuer("http://localhost:9000").build();
	}

	/**
	 * 授权同意服务
	 * @param jdbcTemplate jdbc模板
	 * @param registeredClientRepository 注册客户端库
	 * @return {@link OAuth2AuthorizationConsentService}
	 */
	@Bean
	public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
			RegisteredClientRepository registeredClientRepository) {
		return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
	}

	/**
	 * 授权服务
	 * @param jdbcTemplate jdbc模板
	 * @param registeredClientRepository 注册客户端库
	 * @return {@link OAuth2AuthorizationService}
	 */
	@Bean
	public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
			RegisteredClientRepository registeredClientRepository) {
		return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
	}

}